/* Copyright (C) 2000-2003 MySQL AB

   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA */

#include "mysql_priv.h"
#ifdef HAVE_QUERY_CACHE
#include <mysql.h>
#include "emb_qcache.h"

void Querycache_stream::store_char(char c)
{
  if (data_end == cur_data)
    use_next_block(TRUE);
  *(cur_data++) = c;
#ifndef DBUG_OFF
  stored_size++;
#endif
}

void Querycache_stream::store_short(ushort s)
{
#ifndef DBUG_OFF
  stored_size += 2;
#endif
  if (data_end - cur_data > 1)
  {
    int2store(cur_data, s);
    cur_data += 2;
    return;
  }
  if (data_end == cur_data)
  {
    use_next_block(TRUE);
    int2store(cur_data, s);
    cur_data += 2;
    return;
  }
  *cur_data = ((byte *)(&s))[0];
  use_next_block(TRUE);
  *(cur_data++) = ((byte *)(&s))[1];
}

void Querycache_stream::store_int(uint i)
{
#ifndef DBUG_OFF
  stored_size += 4;
#endif
  size_t rest_len = data_end - cur_data;
  if (rest_len > 3)
  {
    int4store(cur_data, i);
    cur_data += 4;
    return;
  }
  if (!rest_len)
  {
    use_next_block(TRUE);
    int4store(cur_data, i);
    cur_data += 4;
    return;
  }
  char buf[4];
  int4store(buf, i);
  memcpy(cur_data, buf, rest_len);
  use_next_block(TRUE);
  memcpy(cur_data, buf + rest_len, 4 - rest_len);
  cur_data += 4 - rest_len;
}

void Querycache_stream::store_ll(ulonglong ll)
{
#ifndef DBUG_OFF
  stored_size += 8;
#endif
  size_t rest_len = data_end - cur_data;
  if (rest_len > 7)
  {
    int8store(cur_data, ll);
    cur_data += 8;
    return;
  }
  if (!rest_len)
  {
    use_next_block(TRUE);
    int8store(cur_data, ll);
    cur_data += 8;
    return;
  }
  memcpy(cur_data, &ll, rest_len);
  use_next_block(TRUE);
  memcpy(cur_data, ((byte *)&ll) + rest_len, 8 - rest_len);
  cur_data += 8 - rest_len;
}

void Querycache_stream::store_str_only(const char *str, uint str_len)
{
#ifndef DBUG_OFF
  stored_size += str_len;
#endif
  do
  {
    size_t rest_len = data_end - cur_data;
    if (rest_len >= str_len)
    {
      memcpy(cur_data, str, str_len);
      cur_data += str_len;
      return;
    }
    memcpy(cur_data, str, rest_len);
    use_next_block(TRUE);
    str_len -= rest_len;
    str += rest_len;
  } while (str_len);
}

void Querycache_stream::store_str(const char *str, uint str_len)
{
  store_int(str_len);
  store_str_only(str, str_len);
}

void Querycache_stream::store_safe_str(const char *str, uint str_len)
{
  if (str)
  {
    store_int(str_len + 1);
    store_str_only(str, str_len);
  }
  else
    store_int(0);
}

char Querycache_stream::load_char()
{
  if (cur_data == data_end)
    use_next_block(FALSE);
  return *(cur_data++);
}

ushort Querycache_stream::load_short()
{
  ushort result;
  if (data_end - cur_data > 1)
  {
    result = uint2korr(cur_data);
    cur_data += 2;
    return result;
  }
  if (data_end == cur_data)
  {
    use_next_block(FALSE);
    result = uint2korr(cur_data);
    cur_data += 2;
    return result;
  }
  ((byte *)&result)[0] = *cur_data;
  use_next_block(FALSE);
  ((byte *)&result)[1] = *(cur_data++);
  return result;
}

uint Querycache_stream::load_int()
{
  int result;
  size_t rest_len = data_end - cur_data;
  if (rest_len > 3)
  {
    result = uint4korr(cur_data);
    cur_data += 4;
    return result;
  }
  if (!rest_len)
  {
    use_next_block(FALSE);
    result = uint4korr(cur_data);
    cur_data += 4;
    return result;
  }
  char buf[4];
  memcpy(buf, cur_data, rest_len);
  use_next_block(FALSE);
  memcpy(buf + rest_len, cur_data, 4 - rest_len);
  cur_data += 4 - rest_len;
  result = uint4korr(buf);
  return result;
}

ulonglong Querycache_stream::load_ll()
{
  ulonglong result;
  size_t rest_len = data_end - cur_data;
  if (rest_len > 7)
  {
    result = uint8korr(cur_data);
    cur_data += 8;
    return result;
  }
  if (!rest_len)
  {
    use_next_block(FALSE);
    result = uint8korr(cur_data);
    cur_data += 8;
    return result;
  }
  memcpy(&result, cur_data, rest_len);
  use_next_block(FALSE);
  memcpy(((byte *)&result) + rest_len, cur_data, 8 - rest_len);
  cur_data += 8 - rest_len;
  return result;
}

void Querycache_stream::load_str_only(char *buffer, uint str_len)
{
  do
  {
    size_t rest_len = data_end - cur_data;
    if (rest_len >= str_len)
    {
      memcpy(buffer, cur_data, str_len);
      cur_data += str_len;
      buffer += str_len;
      break;
    }
    memcpy(buffer, cur_data, rest_len);
    use_next_block(FALSE);
    str_len -= rest_len;
    buffer += rest_len;
  } while (str_len);
  *buffer = 0;
}

char *Querycache_stream::load_str(MEM_ROOT *alloc, uint *str_len)
{
  char *result;
  *str_len = load_int();
  if (!(result = alloc_root(alloc, *str_len + 1)))
    return 0;
  load_str_only(result, *str_len);
  return result;
}

int Querycache_stream::load_safe_str(MEM_ROOT *alloc, char **str, uint *str_len)
{
  if (!(*str_len = load_int()))
  {
    *str = NULL;
    return 0;
  }
  (*str_len)--;
  if (!(*str = alloc_root(alloc, *str_len + 1)))
    return 1;
  load_str_only(*str, *str_len);
  return 0;
}

int Querycache_stream::load_column(MEM_ROOT *alloc, char **column)
{
  int len;
  if (!(len = load_int()))
  {
    *column = NULL;
    return 0;
  }
  len--;
  if (!(*column = (char *)alloc_root(alloc, len + sizeof(uint) + 1)))
    return 1;
  *((uint *)*column) = len;
  (*column) += sizeof(uint);
  load_str_only(*column, len);
  return 1;
}

uint emb_count_querycache_size(THD *thd)
{
  uint result;
  MYSQL *mysql = thd->mysql;
  MYSQL_FIELD *field = mysql->fields;
  MYSQL_FIELD *field_end = field + mysql->field_count;
  MYSQL_ROWS *cur_row = NULL;
  my_ulonglong n_rows = 0;

  if (!field)
    return 0;
  if (thd->data)
  {
    *thd->data->prev_ptr = NULL;  // this marks the last record
    cur_row = thd->data->data;
    n_rows = thd->data->rows;
  }
  result = (uint)(4 + 8 + (42 + 4 * n_rows) * mysql->field_count);

  for (; field < field_end; field++)
  {
    result += field->name_length + field->table_length + field->org_name_length + field->org_table_length +
              field->db_length + field->catalog_length;
    if (field->def)
      result += field->def_length;
  }

  for (; cur_row; cur_row = cur_row->next)
  {
    MYSQL_ROW col = cur_row->data;
    MYSQL_ROW col_end = col + mysql->field_count;
    for (; col < col_end; col++)
      if (*col)
        result += *(uint *)((*col) - sizeof(uint));
  }
  return result;
}

void emb_store_querycache_result(Querycache_stream *dst, THD *thd)
{
  MYSQL *mysql = thd->mysql;
  MYSQL_FIELD *field = mysql->fields;
  MYSQL_FIELD *field_end = field + mysql->field_count;
  MYSQL_ROWS *cur_row = NULL;
  my_ulonglong n_rows = 0;

  if (!field)
    return;

  if (thd->data)
  {
    *thd->data->prev_ptr = NULL;  // this marks the last record
    cur_row = thd->data->data;
    n_rows = thd->data->rows;
  }

  dst->store_int((uint)mysql->field_count);
  dst->store_ll((uint)n_rows);

  for (; field < field_end; field++)
  {
    dst->store_int((uint)field->length);
    dst->store_int((uint)field->max_length);
    dst->store_char((char)field->type);
    dst->store_short((ushort)field->flags);
    dst->store_short((ushort)field->charsetnr);
    dst->store_char((char)field->decimals);
    dst->store_str(field->name, field->name_length);
    dst->store_str(field->table, field->table_length);
    dst->store_str(field->org_name, field->org_name_length);
    dst->store_str(field->org_table, field->org_table_length);
    dst->store_str(field->db, field->db_length);
    dst->store_str(field->catalog, field->catalog_length);

    dst->store_safe_str(field->def, field->def_length);
  }

  for (; cur_row; cur_row = cur_row->next)
  {
    MYSQL_ROW col = cur_row->data;
    MYSQL_ROW col_end = col + mysql->field_count;
    for (; col < col_end; col++)
    {
      uint len = *col ? *(uint *)((*col) - sizeof(uint)) : 0;
      dst->store_safe_str(*col, len);
    }
  }
  DBUG_ASSERT(emb_count_querycache_size(thd) == dst->stored_size);
}

int emb_load_querycache_result(THD *thd, Querycache_stream *src)
{
  MYSQL *mysql = thd->mysql;
  MYSQL_DATA *data;
  MYSQL_FIELD *field;
  MYSQL_FIELD *field_end;
  MEM_ROOT *f_alloc = &mysql->field_alloc;
  MYSQL_ROWS *row, *end_row;
  MYSQL_ROWS **prev_row;
  ulonglong rows;
  MYSQL_ROW columns;

  mysql->field_count = src->load_int();
  rows = src->load_ll();

  if (!(field = (MYSQL_FIELD *)alloc_root(&mysql->field_alloc, mysql->field_count * sizeof(MYSQL_FIELD))))
    goto err;
  mysql->fields = field;
  for (field_end = field + mysql->field_count; field < field_end; field++)
  {
    field->length = src->load_int();
    field->max_length = (unsigned int)src->load_int();
    field->type = (enum enum_field_types)src->load_char();
    field->flags = (unsigned int)src->load_short();
    field->charsetnr = (unsigned int)src->load_short();
    field->decimals = (unsigned int)src->load_char();

    if (!(field->name = src->load_str(f_alloc, &field->name_length)) ||
        !(field->table = src->load_str(f_alloc, &field->table_length)) ||
        !(field->org_name = src->load_str(f_alloc, &field->org_name_length)) ||
        !(field->org_table = src->load_str(f_alloc, &field->org_table_length)) ||
        !(field->db = src->load_str(f_alloc, &field->db_length)) ||
        !(field->catalog = src->load_str(f_alloc, &field->catalog_length)) ||
        src->load_safe_str(f_alloc, &field->def, &field->def_length))
      goto err;
  }

  if (!rows)
    return 0;
  if (!(data = (MYSQL_DATA *)my_malloc(sizeof(MYSQL_DATA), MYF(MY_WME | MY_ZEROFILL))))
    goto err;
  thd->data = data;
  init_alloc_root(&data->alloc, 8192, 0);
  row = (MYSQL_ROWS *)alloc_root(&data->alloc,
                                 (uint)(rows * sizeof(MYSQL_ROWS) + rows * (mysql->field_count + 1) * sizeof(char *)));
  end_row = row + rows;
  columns = (MYSQL_ROW)end_row;

  data->rows = rows;
  data->fields = mysql->field_count;
  data->data = row;

  for (prev_row = &row->next; row < end_row; prev_row = &row->next, row++)
  {
    *prev_row = row;
    row->data = columns;
    MYSQL_ROW col_end = columns + mysql->field_count;
    for (; columns < col_end; columns++) src->load_column(&data->alloc, columns);

    *(columns++) = NULL;
  }
  *prev_row = NULL;
  data->prev_ptr = prev_row;

  return 0;
err:
  return 1;
}

#endif /*HAVE_QUERY_CACHE*/
